﻿package {
	/**
	 * Classe documento per TicTacToe. Questa classe rappresenta il documento
	 * principale dell'intero gioco.
	 *
	 * @class			TicTacToe
	 * @author			Giovambattista Fazioli
	 * @email			g.fazioli@undolog.com
	 * @web				http://www.undolog.com
	 * @version			1.0
	 *
	 */
	import flash.display.*;
	import flash.events.*;
	import flash.text.*;
	//
	public class TicTacToe extends MovieClip {
		// _______________________________________________________________ STATIC
		
		public static const NAME				:String 	= "TicTacToe";
		public static const VERSION				:String 	= "1.0";
		public static const AUTHOR				:String 	= "Giovambattista Fazioli <g.fazioli@undolog.com>";
		
		static private const OFFSETX			:uint		= 100;
		static private const OFFSETY			:uint		= 25;
		
		static private const CELL_EVENT_COLOR	:Number		= 0xff0000;
		static private const CELL_EVENT_ALPHA	:Number		= 0.1;
		static private const PLAYER_1_COLOR		:Number		= 0x0000aa;
		static private const PLAYER_2_COLOR		:Number		= 0x00aa00;
		static private const PLAYER_LINE_WIDTH	:uint		= 6;
		static private const PLAYER_WIDTH		:uint		= 60;
		static private const PLAYER_HEIGHT		:uint		= 60;
		static private const PLAYER_OFFSET		:uint		= 30;
		
		static private const PLAYER_1_WIN		:uint		= 1;
		static private const PLAYER_2_WIN		:uint		= 10;
		
		static private const PANEL_Y			:uint		= 300;
		
		// ______________________________________________ PRIVATE GLOBAL CLASS VAR
		
		private var __cell						:Array;
		private var __player					:uint;
		private var __clickNumber				:uint		= 0;
		private var __stop						:Boolean	= true;
		
		// ____________________________________________________________ CLASS MAIN
		
		/**
		 * Costruttore della classe
		 */
		public function TicTacToe ():void {
			// imposta stage scale mode e stage align
			stage.scaleMode		= flash.display.StageScaleMode.NO_SCALE
			stage.align			= StageAlign.TOP_LEFT;
			addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		// __________________________________________________________________ INIT
		
		/**
		 * Metodo invocato appena il MovieClip è aggiunto allo stage
		 *
		 * @param		{event}		e=passato da addEventListener(). Può essere null
		 * @return 		void
		 * @private
		 */
		private function init(e:Event = null):void {
			initCell();
		}

		/**
		 * Inizializza un array lineare che rappresenterà la nostra griglia di gioco.
		 * Questa "griglia" viene preimpostata a zero (0), valore che indica che nessun
		 * segno (o/x) è stato fatto.
		 * Al player 1 (o) verrà associato il valore uno (1). Mentre al player 2 (x) verrà
		 * associato il valore dieci (10). Con questo stratagemma quando ad esempio la
		 * somma dei valori presenti nella riga 1 è uguale a 3, sapremo che ha vinto il player 1.
		 * Se la somma, invece, della colonna 1 è 30, allora ha vinto il player 2... e via così.
		 *
		 * 0 | 0 | 0
		 * --+---+---
		 * 0 | 0 | 0
		 * --+---+---
		 * 0 | 0 | 0
 		 *
		 * @param		void
		 * @return 		void
		 * @private
		 */
		private function initCell():void {
			// preimposto l'array di gioco a zero
			__cell			= [0,0,0, 0,0,0, 0,0,0];
			// inizia sempre il giocatore (player) numero 1
			__player		= 1;
			// disegno la griglia a video
			createGrid();
			// mostro a video il turno del giocatore (in questo caso 1)
			turno_txt.text 	= 'PLAYER '+__player;
			// questa variabile è usata per mettere il gioco in pausa se true
			// start-game
			__stop			= false;
		}
		
		/**
		 * Questa funzione controlla l'array per verificare se qualche
		 * giocatore ha vinto. Viene chiamata ogni volta che un giocatore
		 * segna una casella con una (x) o un (o).
		 * Inoltre se la variabile __clickNumber è uguale a 9, significa
		 * che tutte le caselle sono stati riempite e, non essendoci nessun
		 * vincitore, nessuno ha vinto!
		 *
		 * @private
		 */
		private function checkCell():void {
			// in base al valore di __player viene impostato il
			// valore di controllo per verificare una vincita
			var winValue:uint 	= (__player==1)?3*PLAYER_1_WIN:3*PLAYER_2_WIN;
			// controlla tutte le righe, colonne e le due diagonali
			var c:Array = __cell;
			// controllo le prime 3 righe
			if( c[0] + c[1] + c[2] == winValue ||
				c[3] + c[4] + c[5] == winValue ||
				c[6] + c[7] + c[8] == winValue ||
				// le 3 colonne
				c[0] + c[3] + c[6] == winValue ||
				c[1] + c[4] + c[7] == winValue ||
				c[2] + c[5] + c[8] == winValue ||
				// le 2 diagonali
				c[0] + c[4] + c[8] == winValue ||
				c[2] + c[4] + c[6] == winValue ) {
				// se una di queste condizioni è verificata
				// qualcuno ha vinto e mostro quindi il pannello
				// di "vittoria" (1)
				showPanel(1);
			} else {
				// se nessuno ha vinto, controllo se tutte le caselle
				// sono piene. In caso affermativo mostro il pannello
				// di fine partita e "nessun vincitore" (0)
				if( __clickNumber == 9 ) {
					showPanel(0);
				}
			}
		}
		
		/**
		 * Mostra uno dei due pannelli disponibili: vittoria o nessun vincitore
		 *
		 * @param		uint	1=pannello di voncita, 0=nessun vincitore
		 * @return		void
		 *
		 * @private
		 */
		private function showPanel(v:uint):void {
			// metto il gioco in "pausa" quando un pannello è aperto
			__stop				= true;
			// creo uno dei pannelli: v=0 nessun vincitore, v=1 vittoria
			var panel:MovieClip = (v==0)?( new NoWinnerPanel() ):( new WinnerPanel() );
			addChild( panel );
			panel.useHandCursor	= true;
			panel.buttonMode	= true;
			panel.x 			= (stage.stageWidth - panel.width)/2;
			panel.y 			= PANEL_Y;
			// indico al pannello quale giocatore ha vinto
			if( v == 1) panel.player_txt.text = "IL PLAYER "+__player;
			panel.addEventListener(MouseEvent.CLICK,
				function(e:MouseEvent):void {
					// rimuove se stesso
					e.currentTarget.parent.removeChild(e.currentTarget);
					restart();
					turno_txt.text = "PLAYER "+__player;
				}
			);
		}

		/**
		 * Disegno graficamente la griglia (3x3) a video
		 *
		 * @param		void
		 * @return 		void
		 * @private
		 */
		private function createGrid():void {
			// puntatore temporaneo ad uno oggetto MovieClip
			// nota: qui si sarebbe potuto usare anche un oggetto Sprite
			// ma in questo caso mi serve di poter estendere l'oggetto
			// aggiungendo alcune proproetà personali. L'oggetto Sprite
			// è una classe chiusa e quindi non estendibile runtime, mentre
			// la classe MovieClip è una classe dinamica e quindi rende
			// possibile l'aggiunta di proprietà runtime
			var tm:MovieClip, i:uint = 0;
			// aggiungo in uno schema 3x3 i MovieClip
			for(; i < 9; i++) {
				tm					= new MovieClip();
				addChild( tm );
				tm.x 				= OFFSETX+( (i%3)*(PLAYER_WIDTH+PLAYER_OFFSET) )
				tm.y 				= OFFSETY+Math.floor(i/3)*(PLAYER_HEIGHT+PLAYER_OFFSET)
				tm._index 			= i;
				drawPlayer( tm, 0 );
			}
			// disegno le 2 linee veriticali e le 2 orizzontali
			with( this.graphics ) {
				lineStyle(6,0x666666);
				moveTo(OFFSETX,OFFSETY+(PLAYER_HEIGHT+15));
				lineTo(OFFSETX+( (PLAYER_WIDTH+20)*3 ),OFFSETY+(PLAYER_HEIGHT+15));
				moveTo(OFFSETX,OFFSETY+(PLAYER_HEIGHT+20)*2);
				lineTo(OFFSETX+( (PLAYER_WIDTH+20)*3 ),OFFSETY+(PLAYER_HEIGHT+20)*2);
				moveTo(OFFSETX+(PLAYER_WIDTH+15),OFFSETY);
				lineTo(OFFSETX+(PLAYER_WIDTH+15),OFFSETY+( (PLAYER_HEIGHT+20)*3 ));
				moveTo(OFFSETX+(PLAYER_WIDTH+20)*2,OFFSETY);
				lineTo(OFFSETX+(PLAYER_WIDTH+20)*2,OFFSETY+( (PLAYER_HEIGHT+20)*3 ));
			}
		}
		
		/**
		 * Disegna un cerchio o una croce in base al giocatore
		 *
		 * @param	(movieclip)		p=MovieClip dove disegnare
		 * @param	(uint)			pl=numero del giocatore: 1 o 2 - se 0 disegna un rettangolo per intercettare l'evento click
		 */
		private function drawPlayer(p:MovieClip, pl:uint):void {
			switch(pl) {
				// questo è un caso particolare: viene disegnato un rettangolo
				// usato per rilevare il click del mouse
				case 0:
					with(p.graphics) {
						clear();
						beginFill(CELL_EVENT_COLOR, CELL_EVENT_ALPHA);
						drawRect(0,0,60,60);
						endFill();
					}
					p.useHandCursor = p.buttonMode = true;
					// aggiungo l'evento click da questa casella
					p.addEventListener( MouseEvent.CLICK, onCellClick );
					break;
				// player 1 (o)
				case 1:
					with(p.graphics) {
						clear();
						lineStyle(PLAYER_LINE_WIDTH, PLAYER_1_COLOR);
						drawCircle(30,30,30);
					}
					p.useHandCursor = p.buttonMode = false;
					// rimuovo l'evento click da questa casella
					p.removeEventListener(MouseEvent.CLICK, onCellClick);
					break;
				// player 2 (x)	
				case 2:
					with(p.graphics) {
						clear();
						lineStyle(PLAYER_LINE_WIDTH, PLAYER_2_COLOR);
						moveTo(0,0);
						lineTo(60,60);
						moveTo(60,0);
						lineTo(0,60);
					}
					p.useHandCursor = p.buttonMode = false;
					// rimuovo l'evento click da questa casella
					p.removeEventListener(MouseEvent.CLICK, onCellClick);
					break;
			}
		}
		
		/**
		 * Reimposta tutte le variabili per iniziare un nuovo gioco.
		 *
		 * @private
		 */
		private function restart():void {
			// cicla all'interno di questo contenitore e cerca tutti
			// gli oggetti "MovieClip" -  che sappiamo essere le nostre
			// caselle e le "ripulisce" per una nuova partita
			var child:DisplayObject, i:uint = 0;
			for (; i <this.numChildren; i++) {
				child = this.getChildAt(i);
				if( child is MovieClip) {
					drawPlayer(child as MovieClip,0);
				}
			}
			// reimposto (azzero) tutte le variabili di gioco
			__cell			= [0,0,0, 0,0,0, 0,0,0];
			__player		= 1;
			__clickNumber	= 0;
			__stop			= false;
		}
		
		// ________________________________________________________ CALLBACK/EVENT
		
		/**
		 * Questo evento viene rilasciato quando si fa click su una cella di gioco
		 */
		private function onCellClick(e:MouseEvent = null):void {
			// controllo che il gioco non sia in pausa o bloccato
			if( !__stop ) {
				// incremento il contatore dei click
				__clickNumber++;
				// prelevo il puntatore al player "cliccato"
				var p:MovieClip = e.currentTarget as MovieClip;
				// disegno (o) o (x)
				drawPlayer( p, __player );
				// riflette la giocata nell'array lineare
				__cell[p._index] = (__player==1)?PLAYER_1_WIN:PLAYER_2_WIN;
				// controllo se qualcuno ha vinto
				checkCell();
				// cambio turno: se ha giocato il player 1 imposto il player 2 e viceversa
				__player = (__player==1)?2:1;
				// segnalo a video il nuovo turno del giocatore
				turno_txt.text = "PLAYER "+__player;
			}
		}
	}
}